package socket;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;

/*
 聊天室服务器
 */
public class Server {
    /*
     java.net.ServerSocket
     运行在服务端的ServerSocket主要有两个作用
     1 向系统申请固定的服务器端口  客户端Scoket就是通过这个端口进行链接的
     2 监听服务器端口 一旦一个客户端链接机会立即返回一个Scoket 通过它与客户端进行双向交互

     如果我们将
     */
    private ServerSocket serverSocket;
    /*
     该数组用来存放所有ClientHandler对应客户端的输出流 以便广播消息给所有客户端使用
     */
//    private PrintWriter[] allOut = {};
    private Collection<PrintWriter>allOut = new ArrayList<>();

    public Server() {
        try {
            /*
             实例化ServerScoket时要指定服务端口，该端口不能与系统其他应用程序占用的端口相同 负责会抛出异常
             java.net.BindException:address already in use
             若出现该异常 解决办法为更换端口 知道不能抛异常
             */
            System.out.println("正在启动服务器.。。。");
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完成");
        } catch (IOException e) {
        }
    }

    public void start() {
        try {
            while (true) {
                System.out.println("等待客户端链接");
            /*
            j serverSocket提供的方法”
            Socket accept（）
            该方法是一个阻塞方法  调用后开始等待客户端的链接 一旦一个客户端链接那么发该方法会立即返回一个Socket
             */
                Socket socket = serverSocket.accept();
                System.out.println(" 一个客户端链接了！");
                //启动线程与客户端交互
                Runnable r1 = new ClientHandler(socket);
                Thread t1 = new Thread(r1);
                t1.start();}
        } catch (IOException e) {
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    /*
           该线程用于和指定的客户端交互
     */
    private class ClientHandler implements Runnable {
        private final Socket socket;
        private String host;

        public ClientHandler(Socket socket) {
            this.socket = socket;
            host = socket.getInetAddress().getHostAddress();//获取客户端的地址
        }

        public void run() {
            PrintWriter pw = null;
            try {
                InputStream in = socket.getInputStream();
                InputStreamReader is = new InputStreamReader(in);
                BufferedReader br = new BufferedReader(is);

                //通过socket获取输出流  用于给客户端发送消息

                pw = new PrintWriter(
                        new BufferedWriter
                                (new OutputStreamWriter
                                        (socket.getOutputStream(),"UTF-8")),true);

                //将该客户端的输出流存入共享数据
//                synchronized (Server.this) {
                synchronized (allOut){
                   /* allOut = Arrays.copyOf(allOut, allOut.length + 1);
                    allOut[allOut.length - 1] = pw;
                    sendMessage(host + "上线了" + "当前在线人数" + allOut.length);*/
                    allOut.add(pw);
                    sendMessage(host + "上线了" + "当前在线人数" +allOut.size());
                }
                String message;
            /*
             服务端通过缓冲流读取客户端发送过来的一行字符串时 这个方法会产生阻塞  等待对方发送消息 知道对方发过来一行字符串则该方法返回此行内容
            当客户端调用Scoket。close（）断开链接 那么这里的readLine方法会返回null 表述流读取到了末尾（对方断开了链接）
            如果客户端是意外中断 服务器的raedLine会抛出异常
            */
                while ((message = br.readLine()) != null) {
                    System.out.println("客户端说" + message + " ,客户端地址" + host);
                    //遍历allout将消息回复给所有客户端
                   sendMessage(host+"说"+message);
                }
            } catch (IOException e) {
            }finally {
                //客户端断开链接后的操作
                //将客户端的输出流从共享数组里删除
//                synchronized (Server.this){
                /*
                  当临界资源为集合本身时 可以将他直接指定为锁对象
                  因为添加元素并不会创建一个新集合  因此锁对象始终没有发生变化 多个线程看到的始终时同一个集合对象 因此可以保证同步来达到并发安全
                 */
                synchronized (allOut){
                    /* for (int i = 0; i < allOut.length; i++) {
                    if (pw == allOut[i]){
                        allOut[i] = allOut[allOut.length-1];
                        allOut = Arrays.copyOf(allOut,allOut.length-1);
                        break;
                    }
                }*/
                    allOut.remove(pw);
                sendMessage(host+"下线了"+"当前在线人数"+allOut.size());}
                try {
                    socket.close();
                } catch (IOException e) {
                }
            }
        }
        //  将给定的消息转发给所有客户端
        private void sendMessage(String message){
//            synchronized (Server.this) {
            synchronized (allOut){
               /* for (int i = 0; i < allOut.length; i++) {
                    allOut[i].println(message);
                }*/
                for (PrintWriter str : allOut){
                   str.println(message);
                }
            }
        }
    }
}

